package com.sp.business.normalization.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.apache.commons.lang.NullArgumentException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sp.generated.xml.firemon.User;
import com.sp.generated.xml.firemon.UserCollection;

public class UserKeyGenerator
{
  private static final Logger logger = LoggerFactory.getLogger(UserKeyGenerator.class);

  public static void generateUserKeys(UserCollection previousUsers, UserCollection currentUsers)
  {
    if (currentUsers == null)
      throw new NullArgumentException("currentUsers");

    // key statistics
    int matchedKeyCount = 0;
    int generatedKeyCount = 0;

    long start = System.currentTimeMillis();

    List<User> nonMatchedUsers = new ArrayList<User>(currentUsers.getUsers());
    List<User> nonMatchedPreviousUsers;

    if (previousUsers == null)
      nonMatchedPreviousUsers = new ArrayList<User>();
    else
      nonMatchedPreviousUsers = new ArrayList<User>(previousUsers.getUsers());

    // find exact matches first
    for (User user : currentUsers.getUsers())
    {
      User previousUser = findUserExactMatch(nonMatchedPreviousUsers, user);

      if (previousUser == null)
        continue;

      copyUID(previousUser, user);

      // remove the previous user, so it doesn't get matched again
      nonMatchedPreviousUsers.remove(previousUser);

      // remove the user, so we don't try to match it again
      nonMatchedUsers.remove(user);

      matchedKeyCount++;
    }

    // now try to match remaining, non-exact-matched users
    for (User user : nonMatchedUsers)
    {
      User previousUser = findUserCloseMatch(nonMatchedPreviousUsers, user);

      // copy or generate the guid
      copyUID(previousUser, user);

      if (previousUser == null)
      {
        /* no close match could be found for the object.
         * the object is either a newly-created object, 
         * or it has changed so significantly that 
         * it is effectively a new object.
        */
        generatedKeyCount++;
      }
      else
      {
        // we found a close match,
        // remove the previous securityRule, so it doesn't get matched again
        nonMatchedPreviousUsers.remove(previousUser);

        matchedKeyCount++;
      }
    }

    logger.info(String.format("Matched %s and generated %s NetScreen user keys in %s ms.", matchedKeyCount,
        generatedKeyCount, System.currentTimeMillis() - start));
  }

  private static String copyUID(User previous, User current)
  {
    String guid = null;

    if (previous != null)
      guid = previous.getUID();

    if (StringUtils.isBlank(guid) || !NormalizationUtil.isStringUUID(guid))
      guid = String.format("{%s}", UUID.randomUUID().toString().toUpperCase());

    current.setUID(guid);

    current.setXsid("User" + guid.replaceAll("\\{", "").replaceAll("\\}", ""));

    return guid;
  }

  private static User findUserExactMatch(List<User> previousUsers, User user)
  {
    for (User previousUser : previousUsers)
    {
      if (isUserExactMatch(previousUser, user))
        return previousUser;
    }

    return null;
  }

  private static boolean isUserExactMatch(User previousUser, User user)
  {
    // test name
    if (!KeyGeneratorBase.isExactMatch(previousUser.getName(), user.getName()))
      return false;

    // test user type
    if (!KeyGeneratorBase.isExactMatch(previousUser.getType(), user.getType()))
      return false;

    // test administrator
    if (!KeyGeneratorBase.isExactMatch(previousUser.getAdministrator(), user.getAdministrator()))
      return false;

    return true;
  }

  private static User findUserCloseMatch(List<User> previousUsers, User user)
  {
    int lowestEditDistance = Integer.MAX_VALUE;
    User bestMatch = null;

    for (User previousUser : previousUsers)
    {
      // can't change the user type, if the type changes, it's a different user
      if (!KeyGeneratorBase.isExactMatch(previousUser.getType(), user.getType()))
        continue;

      // can't change administrator in netscreen, if it changes, it's a different user
      if (!KeyGeneratorBase.isExactMatch(previousUser.getAdministrator(), user.getAdministrator()))
        continue;

      int editDistance = getEditDistance(previousUser, user);

      if (editDistance < lowestEditDistance)
      {
        lowestEditDistance = editDistance;
        bestMatch = previousUser;
      }
    }

    // TODO: jcoon - need to make this user matching threshold configurable
    float maximumAllowableEditDistance = 50;

    if (lowestEditDistance > maximumAllowableEditDistance)
      return null;

    return bestMatch;
  }

  /**
   * Find the edit distance between two Users.
   * 
   * This is the number of changes needed to change one User into another, 
   * where each change is a single modification (single character difference between names, etc).
   * 
   * @param previousUser
   * @param user
   * @return
   */
  private static int getEditDistance(User previousUser, User user)
  {
    // name
    int editDistance = KeyGeneratorBase.getEditDistance(previousUser.getName(), user.getName());

    return editDistance;
  }
}
